From ba273af5640cd50b64a1f69fd14697fbf6d6b46f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 24 Jul 2014 12:43:10 -0700 Subject: [PATCH] Move from hammer to docopt for option parsing The hammer library currently has some shortcomings such as the inability to document individual options. Additionally our handling with hammer of extra arguments is dodgy at best currently. This commit moves the repository to BurntSushi's docopt.rs library which seems to more feature-complete at this time. Additionally, docopt has the great benefit of a "document once, use everywhere" documentation strategy. This migration solves two primary issues: * Comprehensive and useful CLI documentation * Gracefully handling flavorful combinations of arguments in odd combinations Closes #218 --- Cargo.toml | 10 +- src/bin/cargo-build.rs | 54 ++++----- src/bin/cargo-clean.rs | 31 +++-- src/bin/cargo-doc.rs | 48 ++++---- src/bin/cargo-git-checkout.rs | 25 ++-- src/bin/cargo-new.rs | 47 ++++---- src/bin/cargo-read-manifest.rs | 25 ++-- src/bin/cargo-run.rs | 44 ++++--- src/bin/cargo-rustc.rs | 2 - src/bin/cargo-test.rs | 46 +++---- src/bin/cargo-version.rs | 26 ++-- src/bin/cargo.rs | 174 ++++++++++++++------------- src/cargo/core/shell.rs | 4 + src/cargo/lib.rs | 159 ++++++++++-------------- src/cargo/ops/cargo_rustc/context.rs | 1 - src/cargo/util/errors.rs | 35 +++++- src/etc/install.sh | 4 +- tests/test_cargo_new.rs | 6 +- 18 files changed, 381 insertions(+), 360 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e1f1d190d..a07eacab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,13 @@ name = "cargo" path = "src/cargo/lib.rs" # TODO: remove all these `rev` markers once we have an official lockfile -[dependencies.hammer] -git = "https://github.com/wycats/hammer.rs" -rev = "c085c639" +[dependencies.docopt] +git = "https://github.com/burntsushi/docopt.rs" +rev = "0babd54a" + +[dependencies.docopt_macros] +git = "https://github.com/burntsushi/docopt.rs" +rev = "0babd54a" [dependencies.toml] git = "https://github.com/alexcrichton/toml-rs" diff --git a/src/bin/cargo-build.rs b/src/bin/cargo-build.rs index 349a12621..dba3b55b1 100644 --- a/src/bin/cargo-build.rs +++ b/src/bin/cargo-build.rs @@ -1,15 +1,11 @@ -#![crate_name="cargo-build"] #![feature(phase)] -extern crate cargo; - -#[phase(plugin, link)] -extern crate hammer; - -#[phase(plugin, link)] -extern crate log; - extern crate serialize; +#[phase(plugin, link)] extern crate log; + +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; use std::os; use cargo::{execute_main_without_stdin}; @@ -19,41 +15,45 @@ use cargo::core::MultiShell; use cargo::util::{CliResult, CliError}; use cargo::util::important_paths::{find_root_manifest_for_cwd}; -#[deriving(PartialEq,Clone,Decodable,Encodable)] -pub struct Options { - manifest_path: Option, - update_remotes: bool, - jobs: Option, - target: Option, - release: bool, -} +docopt!(Options, " +Compile a local package and all of its dependencies + +Usage: + cargo-build [options] -hammer_config!(Options "Build the current project", |c| { - c.short("update_remotes", 'u') - .short("jobs", 'j') -}) +Options: + -h, --help Print this message + -j N, --jobs N The number of jobs to run in parallel + --release Build artifacts in release mode, with optimizations + --target TRIPLE Build for the target triple + -u, --update-remotes Update all remote packages before compiling + --manifest-path PATH Path to the manifest to compile + -v, --verbose Use verbose output +", flag_jobs: Option, flag_target: Option, + flag_manifest_path: Option) fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, false); } fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { debug!("executing; cmd=cargo-compile; args={}", os::args()); + shell.set_verbose(options.flag_verbose); - let root = try!(find_root_manifest_for_cwd(options.manifest_path)); + let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path)); - let env = if options.release { + let env = if options.flag_release { "release" } else { "compile" }; let mut opts = CompileOptions { - update: options.update_remotes, + update: options.flag_update_remotes, env: env, shell: shell, - jobs: options.jobs, - target: options.target.as_ref().map(|t| t.as_slice()), + jobs: options.flag_jobs, + target: options.flag_target.as_ref().map(|t| t.as_slice()), }; ops::compile(&root, &mut opts).map(|_| None).map_err(|err| { diff --git a/src/bin/cargo-clean.rs b/src/bin/cargo-clean.rs index dbe597830..f1ca527b6 100644 --- a/src/bin/cargo-clean.rs +++ b/src/bin/cargo-clean.rs @@ -1,15 +1,11 @@ -#![crate_name="cargo-clean"] #![feature(phase)] -extern crate cargo; - -#[phase(plugin, link)] -extern crate hammer; - -#[phase(plugin, link)] -extern crate log; - extern crate serialize; +#[phase(plugin, link)] extern crate log; + +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; use std::os; use cargo::ops; @@ -18,21 +14,24 @@ use cargo::core::MultiShell; use cargo::util::{CliResult, CliError}; use cargo::util::important_paths::{find_root_manifest_for_cwd}; -#[deriving(PartialEq,Clone,Decodable,Encodable)] -pub struct Options { - manifest_path: Option -} +docopt!(Options, " +Usage: + cargo-clean [options] -hammer_config!(Options) +Options: + -h, --help Print this message + --manifest-path PATH Path to the manifest to compile + -v, --verbose Use verbose output +", flag_manifest_path: Option) fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, false); } fn execute(options: Options, _shell: &mut MultiShell) -> CliResult> { debug!("executing; cmd=cargo-clean; args={}", os::args()); - let root = try!(find_root_manifest_for_cwd(options.manifest_path)); + let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path)); ops::clean(&root).map(|_| None).map_err(|err| { CliError::from_boxed(err, 101) diff --git a/src/bin/cargo-doc.rs b/src/bin/cargo-doc.rs index e61c0dd90..c20e99538 100644 --- a/src/bin/cargo-doc.rs +++ b/src/bin/cargo-doc.rs @@ -1,11 +1,9 @@ #![feature(phase)] -#[phase(plugin, link)] -extern crate cargo; extern crate serialize; - -#[phase(plugin, link)] -extern crate hammer; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; use std::os; @@ -15,24 +13,32 @@ use cargo::core::{MultiShell}; use cargo::util::{CliResult, CliError}; use cargo::util::important_paths::find_project_manifest; -#[deriving(PartialEq,Clone,Decodable)] -struct Options { - manifest_path: Option, - jobs: Option, - update: bool, - no_deps: bool, -} +docopt!(Options, " +Build a package's documentation + +Usage: + cargo-doc [options] + +Options: + -h, --help Print this message + --no-deps Don't build documentation for dependencies + -j N, --jobs N The number of jobs to run in parallel + -u, --update-remotes Update all remote packages before compiling + --manifest-path PATH Path to the manifest to compile + -v, --verbose Use verbose output -hammer_config!(Options "Build the package's documentation", |c| { - c.short("jobs", 'j').short("update", 'u') -}) +By default the documentation for the local package and all dependencies is +built. The output is all placed in `target/doc` in rustdoc's usual format. +", flag_jobs: Option, + flag_manifest_path: Option) fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, false) } fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { - let root = match options.manifest_path { + shell.set_verbose(options.flag_verbose); + let root = match options.flag_manifest_path { Some(path) => Path::new(path), None => try!(find_project_manifest(&os::getcwd(), "Cargo.toml") .map_err(|_| { @@ -43,12 +49,12 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { }; let mut doc_opts = ops::DocOptions { - all: !options.no_deps, + all: !options.flag_no_deps, compile_opts: ops::CompileOptions { - update: options.update, - env: if options.no_deps {"doc"} else {"doc-all"}, + update: options.flag_update_remotes, + env: if options.flag_no_deps {"doc"} else {"doc-all"}, shell: shell, - jobs: options.jobs, + jobs: options.flag_jobs, target: None, }, }; diff --git a/src/bin/cargo-git-checkout.rs b/src/bin/cargo-git-checkout.rs index 3e1d601f7..45427234b 100644 --- a/src/bin/cargo-git-checkout.rs +++ b/src/bin/cargo-git-checkout.rs @@ -1,12 +1,12 @@ -#![crate_name="cargo-git-checkout"] #![feature(phase)] -extern crate cargo; extern crate serialize; extern crate url; +#[phase(plugin, link)] extern crate log; -#[phase(plugin, link)] -extern crate hammer; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; use cargo::{execute_main_without_stdin}; use cargo::core::MultiShell; @@ -15,20 +15,21 @@ use cargo::sources::git::{GitSource}; use cargo::util::{Config, CliResult, CliError, Require, human}; use url::Url; -#[deriving(PartialEq,Clone,Decodable)] -struct Options { - url: String, - reference: String -} +docopt!(Options, " +Usage: + cargo-git-checkout [options] --url=URL --reference=REF -hammer_config!(Options) +Options: + -h, --help Print this message + -v, --verbose Use verbose output +") fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, false); } fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { - let Options { url, reference, .. } = options; + let Options { flag_url: url, flag_reference: reference, .. } = options; let url: Url = try!(from_str(url.as_slice()) .require(|| human(format!("The URL `{}` you passed was \ diff --git a/src/bin/cargo-new.rs b/src/bin/cargo-new.rs index efafc54b7..2a40553e5 100644 --- a/src/bin/cargo-new.rs +++ b/src/bin/cargo-new.rs @@ -1,47 +1,44 @@ #![feature(phase)] -extern crate cargo; - -#[phase(plugin, link)] -extern crate hammer; - -#[phase(plugin, link)] -extern crate log; - extern crate serialize; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; +#[phase(plugin, link)] extern crate log; use std::os; use cargo::ops; use cargo::core::MultiShell; use cargo::util::{CliResult, CliError}; -#[deriving(PartialEq,Clone,Decodable,Encodable)] -pub struct Options { - git: bool, - bin: bool, - rest: Vec, -} +docopt!(Options, " +Create a new cargo package at + +Usage: + cargo-new [options] + cargo-new -h | --help -hammer_config!(Options "Create a new cargo project") +Options: + -h, --help Print this message + --git Initialize a new git repository with a .gitignore + --bin Use a binary instead of a library template + -v, --verbose Use verbose output +") fn main() { - cargo::execute_main_without_stdin(execute); + cargo::execute_main_without_stdin(execute, false) } fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { debug!("executing; cmd=cargo-new; args={}", os::args()); + shell.set_verbose(options.flag_verbose); - let Options { git, mut rest, bin } = options; - - let path = match rest.remove(0) { - Some(path) => path, - None => return Err(CliError::new("must have a path as an argument", 1)) - }; + let Options { flag_git, flag_bin, arg_path, .. } = options; let opts = ops::NewOptions { - git: git, - path: path.as_slice(), - bin: bin, + git: flag_git, + path: arg_path.as_slice(), + bin: flag_bin, }; ops::new(opts, shell).map(|_| None).map_err(|err| { diff --git a/src/bin/cargo-read-manifest.rs b/src/bin/cargo-read-manifest.rs index 128aa0745..7cb001652 100644 --- a/src/bin/cargo-read-manifest.rs +++ b/src/bin/cargo-read-manifest.rs @@ -1,30 +1,31 @@ -#![crate_name="cargo-read-manifest"] #![feature(phase)] -extern crate cargo; extern crate serialize; - -#[phase(plugin, link)] -extern crate hammer; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; use cargo::{execute_main_without_stdin}; use cargo::core::{MultiShell, Package, Source}; use cargo::util::{CliResult, CliError}; use cargo::sources::{PathSource}; -#[deriving(PartialEq,Clone,Decodable)] -struct Options { - manifest_path: String -} +docopt!(Options, " +Usage: + cargo-clean [options] --manifest-path=PATH -hammer_config!(Options) +Options: + -h, --help Print this message + -v, --verbose Use verbose output +") fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, false); } fn execute(options: Options, _: &mut MultiShell) -> CliResult> { - let mut source = PathSource::for_path(&Path::new(options.manifest_path.as_slice())); + let path = Path::new(options.flag_manifest_path.as_slice()); + let mut source = PathSource::for_path(&path); try!(source.update().map_err(|err| CliError::new(err.description(), 1))); diff --git a/src/bin/cargo-run.rs b/src/bin/cargo-run.rs index ee017c51e..fbff9ea27 100644 --- a/src/bin/cargo-run.rs +++ b/src/bin/cargo-run.rs @@ -1,11 +1,9 @@ #![feature(phase)] -#[phase(plugin, link)] -extern crate cargo; extern crate serialize; - -#[phase(plugin, link)] -extern crate hammer; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; use std::io::process::ExitStatus; @@ -15,35 +13,41 @@ use cargo::core::{MultiShell}; use cargo::util::{CliResult, CliError}; use cargo::util::important_paths::{find_root_manifest_for_cwd}; -#[deriving(PartialEq,Clone,Decodable)] -struct Options { - manifest_path: Option, - jobs: Option, - update: bool, - rest: Vec, -} +docopt!(Options, " +Run the main binary of the local package (src/main.rs) + +Usage: + cargo-run [options] [--] [...] + +Options: + -h, --help Print this message + -j N, --jobs N The number of jobs to run in parallel + -u, --update-remotes Update all remote packages before compiling + --manifest-path PATH Path to the manifest to compile + -v, --verbose Use verbose output -hammer_config!(Options "Run the package's main executable", |c| { - c.short("jobs", 'j').short("update", 'u') -}) +All of the trailing arguments are passed as to the binary to run. +", flag_jobs: Option, flag_target: Option, + flag_manifest_path: Option) fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, true); } fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { - let root = try!(find_root_manifest_for_cwd(options.manifest_path)); + let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path)); + shell.set_verbose(options.flag_verbose); let mut compile_opts = ops::CompileOptions { - update: options.update, + update: options.flag_update_remotes, env: "compile", shell: shell, - jobs: options.jobs, + jobs: options.flag_jobs, target: None, }; let err = try!(ops::run(&root, &mut compile_opts, - options.rest.as_slice()).map_err(|err| { + options.arg_args.as_slice()).map_err(|err| { CliError::from_boxed(err, 101) })); match err { diff --git a/src/bin/cargo-rustc.rs b/src/bin/cargo-rustc.rs index b2febe003..00a4a2dc3 100644 --- a/src/bin/cargo-rustc.rs +++ b/src/bin/cargo-rustc.rs @@ -1,5 +1,3 @@ -#![crate_name="cargo-rustc"] - extern crate cargo; fn main() { diff --git a/src/bin/cargo-test.rs b/src/bin/cargo-test.rs index 6dccc9aaa..f9c60fc39 100644 --- a/src/bin/cargo-test.rs +++ b/src/bin/cargo-test.rs @@ -1,12 +1,9 @@ -#![crate_name="cargo-test"] #![feature(phase)] -#[phase(plugin, link)] -extern crate cargo; extern crate serialize; - -#[phase(plugin, link)] -extern crate hammer; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; use std::io::process::ExitStatus; @@ -17,30 +14,37 @@ use cargo::util; use cargo::util::{CliResult, CliError, CargoError}; use cargo::util::important_paths::{find_root_manifest_for_cwd}; -#[deriving(PartialEq,Clone,Decodable)] -struct Options { - manifest_path: Option, - jobs: Option, - update: bool, - rest: Vec, -} +docopt!(Options, " +Execute all unit and integration tests of a local package + +Usage: + cargo-test [options] [--] [...] + +Options: + -h, --help Print this message + -j N, --jobs N The number of jobs to run in parallel + -u, --update-remotes Update all remote packages before compiling + --manifest-path PATH Path to the manifest to compile + -v, --verbose Use verbose output -hammer_config!(Options "Run the package's test suite", |c| { - c.short("jobs", 'j').short("update", 'u') -}) +All of the trailing arguments are passed to the test binaries generated for +filtering tests and generally providing options configuring how they run. +", flag_jobs: Option, flag_target: Option, + flag_manifest_path: Option) fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, true); } fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { - let root = try!(find_root_manifest_for_cwd(options.manifest_path)); + let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path)); + shell.set_verbose(options.flag_verbose); let mut compile_opts = ops::CompileOptions { - update: options.update, + update: options.flag_update_remotes, env: "test", shell: shell, - jobs: options.jobs, + jobs: options.flag_jobs, target: None, }; @@ -53,7 +57,7 @@ fn execute(options: Options, shell: &mut MultiShell) -> CliResult> { for file in test_executables.iter() { try!(util::process(test_dir.join(file.as_slice())) - .args(options.rest.as_slice()) + .args(options.arg_args.as_slice()) .exec().map_err(|e| { let exit_status = match e.exit { Some(ExitStatus(i)) => i as uint, diff --git a/src/bin/cargo-version.rs b/src/bin/cargo-version.rs index 8cadd3078..3f0b2e0de 100644 --- a/src/bin/cargo-version.rs +++ b/src/bin/cargo-version.rs @@ -1,29 +1,27 @@ -#![crate_name="cargo-version"] #![feature(phase)] -extern crate cargo; - -#[phase(plugin, link)] -extern crate hammer; - -#[phase(plugin, link)] -extern crate log; - extern crate serialize; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; +#[phase(plugin, link)] extern crate log; use std::os; use cargo::execute_main_without_stdin; use cargo::core::MultiShell; use cargo::util::CliResult; -#[deriving(Decodable,Encodable)] -pub struct Options; +docopt!(Options, " +Usage: + cargo-version [options] -hammer_config!(Options) +Options: + -h, --help Print this message + -v, --verbose Use verbose output +") - fn main() { - execute_main_without_stdin(execute); + execute_main_without_stdin(execute, false); } fn execute(_: Options, _: &mut MultiShell) -> CliResult> { diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index 1f2d22d3a..ed711e2ce 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -1,75 +1,92 @@ #![feature(phase)] -extern crate cargo; -#[phase(plugin, link)] -extern crate hammer; - extern crate serialize; +#[phase(plugin, link)] extern crate log; -#[phase(plugin, link)] -extern crate log; +extern crate cargo; +extern crate docopt; +#[phase(plugin)] extern crate docopt_macros; -use hammer::{FlagConfig,FlagConfiguration}; use std::os; use std::io::process::{Command,InheritFd,ExitStatus,ExitSignal}; use serialize::Encodable; -use cargo::{GlobalFlags, NoFlags, execute_main_without_stdin, handle_error, shell}; +use docopt::FlagParser; + +use cargo::{execute_main_without_stdin, handle_error, shell}; use cargo::core::MultiShell; use cargo::util::important_paths::find_project; use cargo::util::{CliError, CliResult, Require, config, human}; fn main() { - execute(); + execute_main_without_stdin(execute, true) } -#[deriving(Encodable)] -struct ProjectLocation { - root: String -} +docopt!(Flags, " +Rust's package manager + +Usage: + cargo [...] + cargo -h | --help + cargo -V | --version + +Options: + -h, --help Display this message + -V, --version Print version info and exit + -v, --verbose Use verbose output + +Some common cargo commands are: + build Compile the current project + clean Remove the target directory + doc Build this project's and its dependencies' documentation + new Create a new cargo project + run Build and execute src/main.rs + test Run the tests + +See 'cargo help ' for more information on a specific command. +") /** The top-level `cargo` command handles configuration and project location because they are fundamental (and intertwined). Other commands can rely on this top-level information. */ -fn execute() { +fn execute(flags: Flags, shell: &mut MultiShell) -> CliResult> { debug!("executing; cmd=cargo; args={}", os::args()); - let (cmd, args) = process(os::args()); - - match cmd.as_slice() { + shell.set_verbose(flags.flag_verbose); + let mut args = flags.arg_args.clone(); + args.insert(0, flags.arg_command.clone()); + match flags.arg_command.as_slice() { "config-for-key" => { log!(4, "cmd == config-for-key"); - execute_main_without_stdin(config_for_key) + let r = cargo::call_main_without_stdin(config_for_key, shell, + args.as_slice(), false); + cargo::process_executed(r, shell) }, "config-list" => { log!(4, "cmd == config-list"); - execute_main_without_stdin(config_list) + let r = cargo::call_main_without_stdin(config_list, shell, + args.as_slice(), false); + cargo::process_executed(r, shell) }, "locate-project" => { log!(4, "cmd == locate-project"); - execute_main_without_stdin(locate_project) + let r = cargo::call_main_without_stdin(locate_project, shell, + args.as_slice(), false); + cargo::process_executed(r, shell) }, - "--help" | "-h" | "help" | "-?" => { - println!("Commands:"); - println!(" build # compile the current project"); - println!(" test # run the tests"); - println!(" clean # remove the target directory"); - println!(" run # build and execute src/main.rs"); - println!(" version # displays the version of cargo"); - println!(" new # create a new cargo project"); - println!(" doc # build project's rustdoc documentation"); - println!(""); - - - let (_, options) = hammer::usage::(false); - println!("Options (for all commands):\n\n{}", options); - }, - _ => { - // `cargo --version` and `cargo -v` are aliases for `cargo version` - let cmd = if cmd.as_slice() == "--version" || cmd.as_slice() == "-V" { - "version".into_string() + // If we have `help` with no arguments, re-invoke ourself with `-h` to + // get the help message printed + "help" if flags.arg_args.len() == 0 => { + shell.set_verbose(true); + let r = cargo::call_main_without_stdin(execute, shell, + ["-h".to_string()], false); + cargo::process_executed(r, shell) + } + orig_cmd => { + let cmd = if orig_cmd == "help" { + flags.arg_args[0].as_slice() } else { - cmd + orig_cmd }; let command = format!("cargo-{}{}", cmd, os::consts::EXE_SUFFIX); let mut command = match os::self_exe_path() { @@ -86,33 +103,32 @@ fn execute() { } None => Command::new(command), }; - let command = command - .args(args.as_slice()) + let command = if orig_cmd == "help" { + command.arg("-h") + } else { + command.args(flags.arg_args.as_slice()) + }; + let status = command .stdin(InheritFd(0)) .stdout(InheritFd(1)) .stderr(InheritFd(2)) .status(); - match command { + match status { Ok(ExitStatus(0)) => (), Ok(ExitStatus(i)) => { - handle_error(CliError::new("", i as uint), &mut shell(false)) + handle_error(CliError::new("", i as uint), shell) } Ok(ExitSignal(i)) => { let msg = format!("subcommand failed with signal: {}", i); - handle_error(CliError::new(msg, 1), &mut shell(false)) + handle_error(CliError::new(msg, i as uint), shell) } - Err(_) => handle_error(CliError::new("No such subcommand", 127), &mut shell(false)) + Err(_) => handle_error(CliError::new("No such subcommand", 127), + shell) } } } -} - -fn process(args: Vec) -> (String, Vec) { - let mut args = Vec::from_slice(args.tail()); - let head = args.remove(0).unwrap_or("--help".to_string()); - - (head, args) + Ok(None) } #[deriving(Encodable)] @@ -120,52 +136,36 @@ struct ConfigOut { values: std::collections::HashMap } -#[deriving(Decodable)] -struct ConfigForKeyFlags { - key: String, - human: bool -} +docopt!(ConfigForKeyFlags, " +Usage: cargo config-for-key --human --key= +") -impl FlagConfig for ConfigForKeyFlags { - fn config(_: Option, - config: FlagConfiguration) -> FlagConfiguration { - config.short("human", 'h') - } -} - -fn config_for_key(args: ConfigForKeyFlags, _: &mut MultiShell) -> CliResult> { +fn config_for_key(args: ConfigForKeyFlags, + _: &mut MultiShell) -> CliResult> { let value = try!(config::get_config(os::getcwd(), - args.key.as_slice()).map_err(|_| { + args.flag_key.as_slice()).map_err(|_| { CliError::new("Couldn't load configuration", 1) })); - if args.human { + if args.flag_human { println!("{}", value); Ok(None) } else { let mut map = std::collections::HashMap::new(); - map.insert(args.key.clone(), value); + map.insert(args.flag_key.clone(), value); Ok(Some(ConfigOut { values: map })) } } -#[deriving(Decodable)] -struct ConfigListFlags { - human: bool -} - -impl FlagConfig for ConfigListFlags { - fn config(_: Option, - config: FlagConfiguration) -> FlagConfiguration { - config.short("human", 'h') - } -} +docopt!(ConfigListFlags, " +Usage: cargo config-list --human +") fn config_list(args: ConfigListFlags, _: &mut MultiShell) -> CliResult> { let configs = try!(config::all_configs(os::getcwd()).map_err(|_| CliError::new("Couldn't load configuration", 1))); - if args.human { + if args.flag_human { for (key, value) in configs.iter() { println!("{} = {}", key, value); } @@ -175,7 +175,17 @@ fn config_list(args: ConfigListFlags, _: &mut MultiShell) -> CliResult CliResult> { +docopt!(LocateProjectFlags, " +Usage: cargo locate-project +") + +#[deriving(Encodable)] +struct ProjectLocation { + root: String +} + +fn locate_project(_: LocateProjectFlags, + _: &mut MultiShell) -> CliResult> { let root = try!(find_project(&os::getcwd(), "Cargo.toml").map_err(|e| { CliError::from_boxed(e, 1) })); diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index 2aec95ad2..5d98b4c90 100644 --- a/src/cargo/core/shell.rs +++ b/src/cargo/core/shell.rs @@ -67,6 +67,10 @@ impl MultiShell { pub fn warn(&mut self, message: T) -> IoResult<()> { self.err().say(message, YELLOW) } + + pub fn set_verbose(&mut self, verbose: bool) { + self.verbose = verbose; + } } pub type ShellCallback<'a> = |&mut Shell|:'a -> IoResult<()>; diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index c595684c6..b90ebe6e1 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -10,22 +10,18 @@ extern crate collections; extern crate url; extern crate serialize; extern crate semver; -extern crate toml; - -#[phase(plugin, link)] -extern crate hammer; +#[phase(plugin, link)] extern crate log; -#[phase(plugin, link)] -extern crate log; - -#[cfg(test)] -extern crate hamcrest; +extern crate toml; +extern crate docopt; +#[cfg(test)] extern crate hamcrest; -use serialize::{Decoder, Encoder, Decodable, Encodable, json}; -use std::io; -use std::io::{stdout, stderr}; +use std::os; use std::io::stdio::{stdout_raw, stderr_raw}; -use hammer::{Flags, decode_args, usage}; +use std::io::{stdout, stderr}; +use std::io; +use serialize::{Decoder, Encoder, Decodable, Encodable, json}; +use docopt::FlagParser; use core::{Shell, MultiShell, ShellConfig}; use term::color::{BLACK}; @@ -77,87 +73,55 @@ pub mod util; trait RepresentsJSON : Decodable {} impl> RepresentsJSON for T {} -#[deriving(Decodable)] -pub struct NoFlags; - -hammer_config!(NoFlags) - -#[deriving(Show, Decodable)] -pub struct GlobalFlags { - verbose: bool, - help: bool, - rest: Vec -} - -hammer_config!(GlobalFlags |c| { - c.short("verbose", 'v').short("help", 'h') -}) - pub fn execute_main<'a, - T: Flags, + T: FlagParser, U: RepresentsJSON, V: Encodable, io::IoError>>( - exec: fn(T, U, &mut MultiShell) -> CliResult>) { - fn call<'a, - T: Flags, - U: RepresentsJSON, - V: Encodable, io::IoError>>( - exec: fn(T, U, &mut MultiShell) -> CliResult>, - shell: &mut MultiShell, - args: &[String]) -> CliResult> { - let flags = try!(flags_from_args::(args)); - let json = try!(json_from_stdin::()); - - exec(flags, json, shell) - } + exec: fn(T, U, &mut MultiShell) -> CliResult>, + options_first: bool) { + process::(|rest, shell| call_main(exec, shell, rest, options_first)); +} - process::(|rest, shell| call(exec, shell, rest)); +pub fn call_main<'a, + T: FlagParser, + U: RepresentsJSON, + V: Encodable, io::IoError>>( + exec: fn(T, U, &mut MultiShell) -> CliResult>, + shell: &mut MultiShell, + args: &[String], + options_first: bool) -> CliResult> { + let flags = try!(flags_from_args::(args, options_first)); + let json = try!(json_from_stdin::()); + + exec(flags, json, shell) } pub fn execute_main_without_stdin<'a, - T: Flags, + T: FlagParser, V: Encodable, io::IoError>>( - exec: fn(T, &mut MultiShell) -> CliResult>) { - fn call<'a, - T: Flags, - V: Encodable, io::IoError>>( - exec: fn(T, &mut MultiShell) -> CliResult>, - shell: &mut MultiShell, - args: &[String]) -> CliResult> { - let flags = try!(flags_from_args::(args)); - exec(flags, shell) - } + exec: fn(T, &mut MultiShell) -> CliResult>, + options_first: bool) { + process::(|rest, shell| call_main_without_stdin(exec, shell, rest, + options_first)); +} - process::(|rest, shell| call(exec, shell, rest)); +pub fn call_main_without_stdin<'a, + T: FlagParser, + V: Encodable, io::IoError>>( + exec: fn(T, &mut MultiShell) -> CliResult>, + shell: &mut MultiShell, + args: &[String], + options_first: bool) -> CliResult> { + let flags = try!(flags_from_args::(args, options_first)); + exec(flags, shell) } -fn process<'a, - T: Flags, - V: Encodable, io::IoError>>( +fn process<'a, V: Encodable, io::IoError>>( callback: |&[String], &mut MultiShell| -> CliResult>) { - - - match global_flags() { - Err(e) => handle_error(e, &mut shell(false)), - Ok(val) => { - let mut shell = shell(val.verbose); - - if val.help { - let (desc, options) = usage::(true); - - desc.map(|d| println!("{}\n", d)); - - println!("Options:\n"); - - print!("{}", options); - - let (_, options) = usage::(false); - print!("{}", options); - } else { - process_executed(callback(val.rest.as_slice(), &mut shell), &mut shell) - } - } - } + let mut shell = shell(true); + let mut args = os::args(); + args.remove(0); + process_executed(callback(args.as_slice(), &mut shell), &mut shell) } pub fn process_executed<'a, @@ -194,11 +158,11 @@ pub fn shell(verbose: bool) -> MultiShell { pub fn handle_error(err: CliError, shell: &mut MultiShell) { log!(4, "handle_error; err={}", err); - let CliError { error, exit_code, unknown, .. } = err; + let CliError { error, exit_code, unknown } = err; if unknown { let _ = shell.error("An unknown error occurred"); - } else { + } else if error.to_string().len() > 0 { let _ = shell.error(error.to_string()); } @@ -212,6 +176,9 @@ pub fn handle_error(err: CliError, shell: &mut MultiShell) { if unknown { let _ = shell.error(error.to_string()); } + error.detail().map(|detail| { + let _ = shell.err().say(format!("{}", detail), BLACK); + }); error.cause().map(|err| { let _ = handle_cause(err, shell); }); @@ -228,19 +195,21 @@ fn handle_cause(err: &CargoError, shell: &mut MultiShell) { err.cause().map(|e| handle_cause(e, shell)); } -fn args() -> Vec { - std::os::args() -} - -fn flags_from_args(args: &[String]) -> CliResult { - decode_args(args).map_err(|e| { - CliError::new(e.message, 1) - }) +pub fn version() -> String { + (env!("CFG_VERSION")).to_string() } -fn global_flags() -> CliResult { - decode_args(args().tail()).map_err(|e| { - CliError::new(e.message, 1) +fn flags_from_args(args: &[String], + options_first: bool) -> CliResult { + let args = args.iter().map(|a| a.as_slice()).collect::>(); + let config = docopt::Config { + options_first: options_first, + help: true, + version: Some(version()), + }; + FlagParser::parse_args(config, args.as_slice()).map_err(|e| { + let code = if e.fatal() {1} else {0}; + CliError::from_error(e, code) }) } diff --git a/src/cargo/ops/cargo_rustc/context.rs b/src/cargo/ops/cargo_rustc/context.rs index e59df25a6..9f19fab97 100644 --- a/src/cargo/ops/cargo_rustc/context.rs +++ b/src/cargo/ops/cargo_rustc/context.rs @@ -1,5 +1,4 @@ use std::collections::{HashMap, HashSet}; -use std::os; use std::str; use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target}; diff --git a/src/cargo/util/errors.rs b/src/cargo/util/errors.rs index 308ebdeba..93559888d 100644 --- a/src/cargo/util/errors.rs +++ b/src/cargo/util/errors.rs @@ -4,12 +4,13 @@ use std::fmt; use std::fmt::{Show, Formatter, FormatError}; use std::str; +use docopt; use TomlError = toml::Error; pub trait CargoError: Send { fn description(&self) -> String; fn detail(&self) -> Option { None } - fn cause(&self) -> Option<&CargoError + Send> { None } + fn cause(&self) -> Option<&CargoError> { None } fn is_human(&self) -> bool { false } fn to_error>(self) -> E { @@ -78,7 +79,7 @@ impl CargoError for Box { (*self).detail() } - fn cause(&self) -> Option<&CargoError + Send> { + fn cause(&self) -> Option<&CargoError> { (*self).cause() } @@ -194,8 +195,8 @@ impl CargoError for ProcessError { self.detail.clone() } - fn cause(&self) -> Option<&CargoError + Send> { - self.cause.as_ref().map(|c| { let err: &CargoError + Send = *c; err }) + fn cause(&self) -> Option<&CargoError> { + self.cause.as_ref().map(|c| { let err: &CargoError = *c; err }) } fn with_cause(mut self, @@ -227,8 +228,8 @@ impl CargoError for ConcreteCargoError { self.detail.clone() } - fn cause(&self) -> Option<&CargoError + Send> { - self.cause.as_ref().map(|c| { let err: &CargoError + Send = *c; err }) + fn cause(&self) -> Option<&CargoError> { + self.cause.as_ref().map(|c| { let err: &CargoError = *c; err }) } fn with_cause(mut self, @@ -264,6 +265,28 @@ impl CargoError for CliError { from_error!(CliError) +impl CargoError for docopt::Error { + fn description(&self) -> String { + match *self { + docopt::WithProgramUsage(ref other, _) => other.description(), + ref e if e.fatal() => self.to_string(), + _ => "".to_string(), + } + } + + fn detail(&self) -> Option { + match *self { + docopt::WithProgramUsage(_, ref usage) => Some(usage.clone()), + ref e if e.fatal() => None, + ref e => Some(e.to_string()), + } + } + + fn is_human(&self) -> bool { true } +} + +from_error!(docopt::Error) + impl CliError { pub fn new(error: S, code: uint) -> CliError { let error = human(error.as_slice().to_string()); diff --git a/src/etc/install.sh b/src/etc/install.sh index 206adfb27..5eeb316f9 100755 --- a/src/etc/install.sh +++ b/src/etc/install.sh @@ -305,7 +305,7 @@ then if [ -z "${CFG_UNINSTALL}" ] then msg "verifying platform can run binaries" - "${CFG_SRC_DIR}/bin/cargo" -h > /dev/null + "${CFG_SRC_DIR}/bin/cargo" -V > /dev/null if [ $? -ne 0 ] then err "can't execute rustc binary on this platform" @@ -448,7 +448,7 @@ done < "${CFG_SRC_DIR}/${CFG_LIBDIR_RELATIVE}/cargo/manifest.in" if [ -z "${CFG_DISABLE_VERIFY}" ] then msg "verifying installed binaries are executable" - "${CFG_DESTDIR}${CFG_PREFIX}/bin/cargo" -h > /dev/null + "${CFG_DESTDIR}${CFG_PREFIX}/bin/cargo" -V > /dev/null if [ $? -ne 0 ] then ERR="can't execute installed rustc binary. " diff --git a/tests/test_cargo_new.rs b/tests/test_cargo_new.rs index 0b1e87d8c..82a312d51 100644 --- a/tests/test_cargo_new.rs +++ b/tests/test_cargo_new.rs @@ -68,7 +68,11 @@ test!(simple_git { test!(no_argument { assert_that(cargo_process("cargo-new"), execs().with_status(1) - .with_stderr("must have a path as an argument\n")); + .with_stderr("Invalid arguments. +Usage: + cargo-new [options] + cargo-new -h | --help +")); }) test!(existing { -- 2.30.2